20. Асинхронные методы в JavaScript и их применение в Electron

Введение в асинхронность в JavaScript

Асинхронность — это одна из ключевых концепций JavaScript, которая позволяет выполнять задачи без блокировки основного потока выполнения. Это особенно полезно для операций, которые занимают время, таких как запросы к серверу, чтение файлов или выполнение сложных вычислений.

Основные инструменты для работы с асинхронностью:

  1. Callback-функции — функции, которые передаются в качестве аргументов и вызываются после завершения асинхронной операции.
  2. Promises (Обещания) — объекты, которые представляют результат асинхронной операции, которая может завершиться успешно или с ошибкой.
  3. Async/Awaitсинтаксический сахар для работы с Promises, который делает асинхронный код более читаемым.

Пример асинхронного кода в JavaScript

1. Использование Callback-функций

function fetchData(callback) {
    setTimeout(() => {
        callback("Данные получены!");
    }, 1000);
}

fetchData((data) => {
    console.log(data); // "Данные получены!" через 1 секунду
});

2. Использование Promises

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("Данные получены!");
        }, 1000);
    });
}

fetchData()
    .then((data) => {
        console.log(data); // "Данные получены!" через 1 секунду
    })
    .catch((error) => {
        console.error(error);
});

3. Использование Async/Await

async function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("Данные получены!");
        }, 1000);
    });
}

async function main() {
    try {
        const data = await fetchData();
        console.log(data); // "Данные получены!" через 1 секунду
    } catch (error) {
        console.error(error);
    }
}

main();

Как работает async?

Функция, объявленная с async, всегда возвращает Promise.

Пример 1: async всегда возвращает Promise

async function getNumber() {
    return 42;
}
getNumber().then(console.log); // Выведет 42

Здесь getNumber() вернет Promise, который имеет значение 42.

Как работает await?

Ключевое слово await заставляет JavaScript ждать, пока Promise выполнится, и только после этого продолжает выполнение кода.
⚠️ Важно: await можно использовать только внутри async-функции.

Пример 2: Использование await

async function fetchData() {
    console.log("Запрос данных...");
    let result = await new Promise(resolve => setTimeout(() => resolve("Данные получены!"), 2000));
    console.log(result);
}
fetchData();
console.log("Этот код выполнится сразу, не дожидаясь fetchData()");

Что произойдет?

  1. fetchData() запустится и выведет "Запрос данных...".
  2. JavaScript не будет ждать завершения fetchData() и сразу выполнит console.log("Этот код выполнится сразу...").
  3. Через 2 секунды в консоли появится "Данные получены!".

Как async/await заменяет then/catch?

Рассмотрим пример с fetch, который делает HTTP-запрос.

Современный способ: async/await

async function getTodo() {
    try {
        let response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
        let data = await response.json();
        console.log(data);
    } catch (error) {
        console.error("Ошибка:", error);
    }
}
getTodo();

Обработка нескольких await подряд

Если нужно выполнить несколько await, они выполняются по очереди, что может замедлять код.

Плохой пример (медленный код)

async function getData() {
    let user = await fetch("https://jsonplaceholder.typicode.com/users/1").then(res => res.json());
    let posts = await fetch("https://jsonplaceholder.typicode.com/posts?userId=1").then(res => res.json());
    console.log(user, posts);
}
getData();

Здесь второй fetch начнется только после завершения первого, что неэффективно.

Оптимизированный вариант (параллельные запросы)

async function getData() {
    let [user, posts] = await Promise.all([
        fetch("https://jsonplaceholder.typicode.com/users/1").then(res => res.json()),
        fetch("https://jsonplaceholder.typicode.com/posts?userId=1").then(res => res.json())
    ]);
    console.log(user, posts);
}
getData();

Теперь оба запроса выполняются параллельно, экономя время.

Вывод

  • async делает функцию асинхронной и всегда возвращает Promise.
  • await заставляет код ждать выполнения Promise перед продолжением.
  • try/catch удобнее для обработки ошибок, чем .catch().
  • Promise.all() помогает ускорить выполнение нескольких await.

Применение асинхронности в Electron с использованием Context Isolation

Теперь, когда мы разобрались с асинхронностью в JavaScript, давайте рассмотрим, как её можно использовать в Electron-приложении с включённой Context Isolation.

Шаг 1: Настройка проекта Electron

  1. Создайте базовое Electron-приложение с включённой contextIsolation и отключённой nodeIntegration.
  2. Добавьте файл предзагрузки (preload.js), который будет предоставлять рендереру доступ к асинхронным методам.

Пример index.js:

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');

function createWindow() {
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'),
            contextIsolation: true,
            nodeIntegration: false,
        },
    });

    mainWindow.loadFile('index.html');
}

app.whenReady().then(() => {
    createWindow();

    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) {
            createWindow();
        }
    });
});

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

Пример preload.js:

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('YourApp', {
    performAsyncTask: () => ipcRenderer.invoke('perform-async-task'),
});
contextBridge.exposeInMainWorld('YourApp', {
    performAsyncTask: async () => await ipcRenderer.invoke('perform-async-task'),
});

Шаг 2: Реализация асинхронной задачи в основном процессе

В основном процессе создадим асинхронную задачу, которая будет выполняться с задержкой и возвращать результат в рендерер.

Пример асинхронной задачи в index.js:

ipcMain.handle('perform-async-task', async (e, data) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve("Асинхронная задача завершена!");
        }, 2000); // 2000 мс == 2 с
    });
});

Шаг 3: Использование асинхронной задачи в рендерере

Теперь, когда асинхронная задача настроена, мы можем вызвать её из рендерера и отобразить результат.

Пример renderer.js:

async function runTask() {
    try {
        const result = await window.YourApp.performAsyncTask();
        console.log(result); // "Асинхронная задача завершена!" через 2 секунды
        document.getElementById('result').innerText = result;
    } catch (error) {
        console.error('Ошибка:', error);
    }
}

document.getElementById('startTask').addEventListener('click', runTask);

Пример index.html:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Асинхронные задачи в Electron</title>
</head>
<body>
    <h1>Асинхронные задачи в Electron</h1>
    <button id="startTask">Запустить задачу</button>
    <p id="result"></p>
    <script src="renderer.js"></script>
</body>
</html>

Практическое задание

  1. Создайте Electron-приложение с включённой contextIsolation и отключённой nodeIntegration.
  2. Реализуйте асинхронную задачу в основном процессе, которая имитирует загрузку данных с сервера (например, с задержкой в 2 секунды).
  3. Отобразите результат выполнения задачи в интерфейсе приложения.
  4. Добавьте обработку ошибок на случай, если задача завершится с ошибкой.

Заключение

Асинхронные методы в JavaScript — это мощный инструмент для выполнения задач без блокировки основного потока. В Electron с использованием Context Isolation можно безопасно выполнять асинхронные задачи, передавая данные между основным процессом и рендерером. В этом занятии мы рассмотрели, как реализовать асинхронные задачи в Electron и отобразить их результаты в интерфейсе приложения.